/**
 * OSS is a Open Service Container which gives the leverage of building the Desktop and Web
 * Application. * Copyright (C) 2008-2009  Ponraj Sutanthiramani
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Or5e Open Source Community
 * 105, S.S Paradise Apartment, Munnireddy Layout, Horamavu, Banaswadi, Bangalore, India.
 */
package org.or5e.api.webcam;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Codec;
import javax.media.ConfigureCompleteEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.EndOfMediaEvent;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.NotConfiguredError;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.UnsupportedPlugInException;
import javax.media.control.TrackControl;
import javax.media.format.VideoFormat;

import org.or5e.core.BaseException;
import org.or5e.core.BaseObject;
import org.or5e.core.BaseUtils;

/**
 * @author Ponraj Suthanthiramani
 */
public final class WebCameraSPI extends BaseObject implements WebCamera, ControllerListener {
    protected static Map<String, Processor> processorMap = new HashMap<String, Processor>();
    private BaseUtils utils = null;
    private static WebCamera webcamera = null;
    private static Boolean isInitilized = Boolean.FALSE;
    private final Object waitSync = new Object();
    boolean stateTransitionOK = true;

    private WebCameraSPI() {
        info("*** Creating and Initilizing the WebCamera API");
        utils = BaseUtils.getUtility();
        Runtime.getRuntime().addShutdownHook(new Thread(new CameraShutdownHook()));
    }

    public static WebCamera getWebCamera() {
        if(webcamera == null) webcamera = new WebCameraSPI();
        return webcamera;
    }

    @Override
    public boolean initilize(String[] deviceList) throws WebCameraException {
        if(isInitilized) throw new WebCameraException(WebCameraExceptionConstants.E004);

        info("*** Initilzing the Web Camera devices...");
        if(utils.isNotEmptyArray(deviceList)) {
            for (String device : deviceList) {
                if(initilizeDevice(device)) continue ;
            }
        }
        info("WebCamera API got initilized...");
        isInitilized = Boolean.TRUE;
        return true;
    }

    @Override
    public Set<String> getVideoDevices() throws WebCameraException {
        if(!isInitilized) throw new WebCameraException(WebCameraExceptionConstants.E001);
        if(processorMap != null && processorMap.size() > 0 ) {
            return processorMap.keySet();
        }
        return null;
    }

    @Override
    public void startDevice(String videoDevice) throws WebCameraException {
        if(!isInitilized) throw new WebCameraException(WebCameraExceptionConstants.E001);
        if(processorMap != null && processorMap.containsKey(videoDevice)) {
            Processor startDevice = processorMap.get(videoDevice);
            int status = startDevice.getState();
            if(status != Processor.Started) {
                startDevice.start();
            }
            else {
                throw new WebCameraException(WebCameraExceptionConstants.W001);
            }
        }
        else {
            throw new WebCameraException(WebCameraExceptionConstants.E002);
        }
    }

    @Override
    public void stopDevice(String videoDevice) throws WebCameraException {
        if(!isInitilized) throw new WebCameraException(WebCameraExceptionConstants.E001);
        if(processorMap != null && processorMap.containsKey(videoDevice)) {
            Processor stopDevice = processorMap.get(videoDevice);
            int status = stopDevice.getState();
            if(status == 0) {
                stopDevice.stop();
            }
            else {
                throw new WebCameraException(WebCameraExceptionConstants.W002);
            }
        }
        else {
            throw new WebCameraException(WebCameraExceptionConstants.E002);
        }
    }

    @Override
    public void destroy() throws WebCameraException {
        if(processorMap != null && processorMap.size() > 0) {
            Set<String> keyList = processorMap.keySet();
            for (String device : keyList) {
                processorMap.get(device).stop();
                processorMap.get(device).deallocate();
                processorMap.remove(device);
            }
        }
    }

    @Override
    public void controllerUpdate(ControllerEvent evt) {
        if (evt instanceof ConfigureCompleteEvent ||
                evt instanceof RealizeCompleteEvent ||
                evt instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
                stateTransitionOK = true;
                waitSync.notifyAll();
            }
        } else if (evt instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
                stateTransitionOK = false;
                waitSync.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) {
//            p.close();
//            System.exit(0);
        }

    }

    @Override
    public Processor getProcessor(String videoDevice) {
        return (processorMap != null && processorMap.containsKey(videoDevice))?
            processorMap.get(videoDevice):
            null;
    }

    private boolean initilizeDevice(String device) throws WebCameraException, NotConfiguredError {
        CaptureDeviceInfo deviceInfo = CaptureDeviceManager.getDevice(device);
        info("Device Info Captured...");
        if (deviceInfo != null) {
            try {
                MediaLocator mediaLocator = deviceInfo.getLocator();
                info("Media Locator Created...");
                if (mediaLocator == null) {
                    return true;
                }
                Processor processor = Manager.createProcessor(mediaLocator);
                info("Processor Created...");
                processor.addControllerListener(this);
                processor.configure();
                if (!waitForState(Processor.Configured, processor)) {
                    System.err.println("Failed to configure the processor.");
                    return true;
                }
                processor.setContentDescriptor(null);
                TrackControl[] tc = processor.getTrackControls();
                if (tc == null) {
                    System.err.println("Failed to obtain track controls from the processor.");
                    return true;
                }
                info("Track Control Created...");
                TrackControl videoTrack = null;
                for (int i = 0; i < tc.length; i++) {
                    if (tc[i].getFormat() instanceof VideoFormat) {
                        videoTrack = tc[i];
                        System.out.println("VideoTrack Format: " + videoTrack.getFormat());
                        break;
                    }
                }
                info("Track Video Created...");
                if (videoTrack == null) {
                    System.err.println("The input media does not contain a video track.");
                    return true;
                }
                try {
                    Codec[] codec = {new PostAccessCodec()};
                    videoTrack.setCodecChain(codec);
                } catch (UnsupportedPlugInException e) {
                    System.err.println("The process does not support effects.");
                }
                info("Processor Prefetch...");
                processor.prefetch();
                if (!waitForState(Processor.Prefetched, processor)) {
                    System.err.println("Failed to realize the processor.");
                    return true;
                }
                info("Task done...");
                processorMap.put(device, processor);
            } catch (IOException ex) {
                error("Exception occured while creating the processor." + ex.getMessage());
            } catch (NoProcessorException ex) {
                error("Exception occured while creating the processor." + ex.getMessage());
            }
        } else {
            throw new WebCameraException(WebCameraExceptionConstants.E003);
        }
        return false;
    }
    private boolean waitForState(int state, Processor processor) {
        synchronized (waitSync) {
            try {
                while (processor.getState() != state && stateTransitionOK) {
                    waitSync.wait();
                }
            }
            catch (Exception ex) {
                error("*** Exception Occured in the Wait For State :"+ex.getMessage());
            }
        }
        return stateTransitionOK;
    }

    @Override
    public String getName() {
        return "WebCameraSpi";
    }

    @Override
    public boolean addDevice(String deviceList) throws WebCameraException {
        return !initilizeDevice(deviceList);
    }

    class CameraShutdownHook extends BaseObject implements Runnable {

        @Override
        public String getName() {
            return "CameraShutdownHook";
        }

        @Override
        public void run() {
            info("*** Shutting down the Camera SPI gracefully....");
            try {
                destroy();
            }
            catch (WebCameraException ex) {
                error("*** Exception raised when shutting down the Camera SPI :"+ex.getMessage());
            }
        }
    }

	@Override
	public void install() throws BaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void uninstall() throws BaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void start() throws BaseException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stop() throws BaseException {
		// TODO Auto-generated method stub
		
	}
}